import tkinter as tk
from tkinter import ttk
import pandas as pd
import plotly.express as px
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import random
import plotly.graph_objects as go
from plotly.offline import init_notebook_mode
import matplotlib.pyplot as plt
from wordcloud import WordCloud

class WordObject:
    def __init__(self, canvas, word, x, y, size, color):
        self.canvas = canvas
        self.word = word
        self.x = x
        self.y = y
        self.size = size
        self.color = color
        self.id = None
        # 确保速度不为0
        self.dx = random.randint(-3, 3)
        while self.dx == 0:
            self.dx = random.randint(-3, 3)
        self.dy = random.randint(-3, 3)
        while self.dy == 0:
            self.dy = random.randint(-3, 3)

    def draw(self):
        # 加粗显示
        self.id = self.canvas.create_text(self.x, self.y, 
                                        text=self.word, 
                                        font=("Arial", self.size, "bold"),  # 添加bold属性
                                        fill=self.color)
        
    def move(self):
        # 移动单词
        self.x += self.dx
        self.y += self.dy
        
        # 增加反弹极限距离
        padding = 50  # 增加50像素的缓冲区
        # 单词的左边界和右边界
        if self.x -padding < 0 or self.x + self.size * 0.5 + padding> 800 :
            self.dx = -self.dx
        # 单词的上边界和下边界
        if self.y -padding < 0 or self.y + self.size * 0.5 + padding> 600 :
            self.dy = -self.dy
            
        try:
            self.canvas.coords(self.id, self.x, self.y)
        except:
            pass
        
    def move_to(self, x, y):
        self.x = x
        self.y = y
        try:
            self.canvas.coords(self.id, self.x, self.y)
        except:
            pass
        
    def contains(self, x, y):
        return (abs(x - self.x) < self.size/2) and (abs(y - self.y) < self.size/2)
class SentimentApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Sentiment Analysis")
        
        # 初始化数据
        self.load_data()
        
        # 创建主界面
        self.create_main_interface()
        
    def load_data(self):
        '''split'''
        def spl(allSt):
            allSt['content']=allSt['content'].str.replace(r'\@\S+\s','', regex=True).str.replace(r'[^A-Za-z\s\d]','', regex=True).str.lower().apply(lambda x: [word for word in x.split() if word not in self.swl])
            pf=pd.DataFrame({'word':allSt['content'].explode()}).join(allSt.drop('content',axis=1))
            dic={}
            for i in allSt["sentiment"].unique():
                dic[i]=pf[pf['sentiment']==i]
            ct={sen:df['word'].value_counts() for sen,df in dic.items()}
            return ct
        '''normalize (preparation for filtering out common words with no emotional inclinations)'''
        def nor(ct):
            norCt={}
            for sen,freqs in ct.items():
                minFreq=freqs.min()
                maxFreq=freqs.max()
                norFreqs=((freqs-minFreq)/(maxFreq-minFreq))*100
                #100/(maxFreq-minFreq) is the normalization ratio, because the normalization range is [0,100]
                norCt[sen]=norFreqs
            return norCt
        '''filter out common words'''
        def com(norCt,ct):
            combined=pd.concat(norCt.values()).apply(lambda x:(int(x) if type(x)==float else x))
            ctDf=pd.concat(ct,axis=1)
            stat=combined.groupby(combined.index).agg(['min','max','count',lambda x:x.mode().iloc[0] if not x.mode().empty else None]).rename(columns={'<lambda_0>':'mode'})
            toRem=stat[(stat['count']>=5)&(((stat['max']-stat['min'])<=10)|((stat['max']-stat['mode'])<=10))].index
            #if the frequency is not less than 5 and any value within (range,max-mode-distance) of its counts is not greater than 10, then filter this word out from all ctwords lists
            ctDf=ctDf.drop(index=toRem,errors='ignore')
            ct={sen:ctDf[sen].dropna().apply(lambda x:(int(x) if type(x)==float else x)).sort_values(ascending=False).head(10) for sen in ctDf.columns}
            #print('Removed:',toRem)
            return ct
        stopwords = pd.read_csv('stopwords.csv')['sw']
        self.swl = set(stopwords.tolist())
        self.allSt = pd.read_csv('./tweet_emotions.csv')
        self.ct = spl(self.allSt)
        self.norCt = nor(self.ct)
        self.result = com(self.norCt, self.ct)
        
        # 生成情感分布数据
        self.sentiment_counts = self.allSt['sentiment'].value_counts()
        
        # 计算所有单词的总词频
        self.all_words = {}
        for emotion, data in self.result.items():
            for word in data.index:
                freq = data[word]
                if word in self.all_words:
                    self.all_words[word] += freq
                else:
                    self.all_words[word] = freq
        
    def create_main_interface(self):
        # 创建主界面容器
        main_frame = tk.Frame(self.root)
        main_frame.pack(padx=10, pady=10)
        
        sentiment_to_word = ttk.Button(main_frame, text="Sentiment->Word", command=self.show_sentiment_word)
        sentiment_to_word.pack(side=tk.LEFT, padx=10, pady=10)
        
        word_to_sentiment = ttk.Button(main_frame, text="Word->Sentiment", command=self.show_word_sentiment)
        word_to_sentiment.pack(side=tk.RIGHT, padx=10, pady=10)
        
    def show_sentiment_word(self):
        # 创建新窗口
        sentiment_window = tk.Toplevel(self.root)
        sentiment_window.title("Sentiment -> Word")
        sentiment_window.geometry("1200x1000")
        
        # 创建左侧画布显示饼图
        left_frame = tk.Frame(sentiment_window)
        left_frame.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 创建右侧列表显示情感类别
        right_frame = tk.Frame(sentiment_window)
        right_frame.pack(side=tk.RIGHT, padx=10, pady=10)
        
        # 创建饼图
        emotion_colors = {
            'neutral': 'green',
            'worry': 'gray',
            'happiness': 'yellow',
            'sadness': 'blue',
            'love': 'pink',
            'surprise': 'gold',
            'fun': 'purple',
            'relief': 'lightblue',
            'hate': 'darkblue',
            'enthusiasm': 'orange',
            'boredom': 'brown',
            'anger': 'red'
        }
        
        colors = [emotion_colors.get(emotion, 'gray') for emotion in self.sentiment_counts.index]
        
        fig, ax = plt.subplots(figsize=(8, 8))
        ax.pie(self.sentiment_counts, labels=self.sentiment_counts.index, autopct='%1.1f%%', startangle=140, colors=colors)
        ax.set_title('Emotion Distribution')
        
        # 将图表添加到左侧画布
        canvas = FigureCanvasTkAgg(fig, master=left_frame)
        canvas.draw()
        canvas.get_tk_widget().pack()
        
        # 创建情感类别列表
        for sentiment in self.result.keys():
            sentiment_box = ttk.Button(right_frame, text=sentiment, command=lambda s=sentiment: self.show_sentiment_details(s))
            sentiment_box.pack(fill=tk.X, pady=2)
            
    def show_sentiment_details(self, sentiment):
        detail_window = tk.Toplevel(self.root)
        detail_window.title(f"{sentiment} Details")
        
        left_frame = tk.Frame(detail_window)
        left_frame.pack(side=tk.LEFT, padx=10, pady=10)
        
        right_frame = tk.Frame(detail_window)
        right_frame.pack(side=tk.RIGHT, padx=10, pady=10)
        
        fig_pie, ax = plt.subplots()
        top_words = self.result[sentiment].head(10)
        labels = [f"{word}: {count}" for word, count in top_words.items()]
        ax.pie(top_words, labels=labels, autopct='%1.1f%%', startangle=90)
        ax.set_title(f'Emotion: {sentiment} - Top 10')
        
        canvas = FigureCanvasTkAgg(fig_pie, master=left_frame)
        canvas.draw()
        canvas.get_tk_widget().pack()
        
        word_freq = dict(zip(top_words.index, top_words.values))
        wordcloud = WordCloud(width=400, height=400, background_color='white').generate_from_frequencies(word_freq)
        
        plt.figure(figsize=(5, 5))
        plt.imshow(wordcloud, interpolation='bilinear')
        plt.axis("off")
        plt.title(f'{sentiment} Emotion Word Cloud')
        canvas = FigureCanvasTkAgg(plt.gcf(), master=right_frame)
        canvas.draw()
        canvas.get_tk_widget().pack()
        
    def show_word_sentiment(self):
        self.word_window = tk.Toplevel(self.root)
        self.word_window.title("Word -> Sentiment")
        self.word_window.geometry("800x600")
        
        # 创建词云布局
        self.canvas = tk.Canvas(self.word_window, width=800, height=600, bg='white')
        self.canvas.pack()
        
        # 计算词频比例
        max_freq = max(self.all_words.values())
        min_freq = min(self.all_words.values())
        
        # 创建单词对象
        self.word_objects = []
        self.selected_word = None
        
        
        # 获取词云中的位置信息
        word_positions = {}
        for word, freq in self.all_words.items():
            x = random.randint(100, 600)
            y = random.randint(100, 400)
            word_positions[word] = (x, y)
        
        for word, freq in self.all_words.items():
            # 计算单词大小
            size = int(15 + (freq - min_freq) * 30 / (max_freq - min_freq))
            size = max(15, min(70, size))  # 调整了大小范围
            
            # 获取词云中的位置
            x, y = word_positions[word]
            
            # 生成颜色
            color = self.get_random_color()
            
            # 创建单词对象
            word_obj = WordObject(self.canvas, word, x, y, size, color)
            word_obj.draw()
            self.word_objects.append(word_obj)
        
        # 绑定事件
        self.canvas.bind("<Button-1>", self.on_click)
        self.canvas.bind("<B1-Motion>", self.on_drag)
        
        # 开始动画
        self.animate()
        
    def animate(self):
        if not self.word_window.winfo_exists():
            return
            
        # 使单词在画布上移动
        for word_obj in self.word_objects:
            word_obj.move()
        
        # 每50毫秒更新一次
        self.word_window.after(50, self.animate)
        
    def on_click(self, event):
        # 查找点击的单词
        for word_obj in self.word_objects:
            if word_obj.contains(event.x, event.y):
                self.selected_word = word_obj.word
                self.show_word_details(word_obj.word)
                break
        
    def on_drag(self, event):
        # 拖动单词
        if self.selected_word:
            for word_obj in self.word_objects:
                if word_obj.word == self.selected_word:
                    word_obj.move_to(event.x, event.y)
                    break
        
    def show_word_details(self, word):
        detail_window = tk.Toplevel(self.root)
        detail_window.title(f"Word Details: {word}")
        
        detail_frame = tk.Frame(detail_window)
        detail_frame.pack(padx=10, pady=10)
        
        word_distribution = {}
        for sentiment, words in self.result.items():
            if word in words.index:
                word_distribution[sentiment] = words[word]
        
        sorted_distribution = sorted(word_distribution.items(), key=lambda x: x[1], reverse=True)
        
        for sentiment, count in sorted_distribution:
            label = ttk.Label(detail_window, text=f"{sentiment}: {count}")
            label.pack(fill=tk.X, pady=2)
            
    def get_random_color(self):
        r = random.randint(0, 255)
        g = random.randint(0, 255)
        b = random.randint(0, 255)
        return f'#{r:02x}{g:02x}{b:02x}'
if __name__ == "__main__":
    root = tk.Tk()
    app = SentimentApp(root)
    root.mainloop()
